package com.gmrz.uaf.protocol.v1.processor;

import com.gmrz.service.challengestore.ChallengeStoreException;
import com.gmrz.uaf.common.*;
import com.gmrz.uaf.crypto.sm.SM2Utils;
import com.gmrz.uaf.db.DAOException;
import com.gmrz.uaf.db.UAFDAOFactory;
import com.gmrz.uaf.db.UAFDBConnectionMgr;
import com.gmrz.uaf.db.dao.AuthenticatorDAO;
import com.gmrz.uaf.policy.verification.PolicyVerificationException;
import com.gmrz.uaf.policy.verification.PolicyVerificationProcessor;
import com.gmrz.uaf.protocol.v1.json.UAFSchemaBuilder;
import com.gmrz.uaf.protocol.v1.processor.exception.*;
import com.gmrz.uaf.protocol.v1.schema.*;
import com.gmrz.uaf.protocol.v1.validaton.ValidationException;
import com.gmrz.uaf.protocol.v1.validaton.Validator;
import com.gmrz.uaf.protocol.v1.validaton.auth.AuthResponseValidator;
import com.gmrz.uaf.protocol.v1.validaton.auth.AuthSignAssertionValidationException;
import com.gmrz.uaf.protocol.v1.validaton.auth.AuthSignAssertionValidator;
import com.gmrz.uaf.server.ServerContext;
import com.gmrz.uas.plugin.exception.PluginException;
import com.gmrz.util.Convert;
import com.gmrz.util.CryUtil;
import com.gmrz.util.Strings;
import com.gmrz.util.db.DBUtil;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.io.IOException;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.*;

public class UAFAuthFinishProcessor extends UAFBaseProcessor {

    private static final Logger LOG = LogManager.getLogger(UAFAuthFinishProcessor.class);


    public void process(AuthenticationResponse response, String appID, String hashedUserName, String authType, String transType,String ext,String functionType)
            throws UAFException, ChallengeStoreException {

        LOG.info("Validating authentication response");

        Validator<AuthenticationResponse> authRespValidator = new AuthResponseValidator(this.serverConfig,
                Constants.Operation.AUTH, appID,transType);
        ServerData serverData = response.getOperationHeader().getServerData();
        if (this.getServerConfig().getPerformModel() < Constants.PERFORM_MODEL_LEVEL_2 && !ServerContext.getServerContext().isPerform()) {
            authRespValidator.validate(response);
            LOG.info("Validation completed authentication response");
            // 校验重放攻击
            checkForReplayAttack(serverData);

            List<Extension> exts = response.getOperationHeader().getExtensions();
            // 处理扩展数据
            processExtensions(exts);
        }
        TLSData tlsData = response.getFinalChallengeParams().getTLSData();
        processTLSData(tlsData);

        String plainUserName = serverData.getUserName();

        String policyName = serverData.getPolicyName();
        LOG.info("Auth Response userAliasName[{}], policyname[{}]", plainUserName, policyName);

        List<AuthenticationAssertion> assertions = response.getAuthenticatorAssertions();
        if ((assertions == null) || (assertions.isEmpty())) {
            String msg = "No assertion found in the registration response for user[" + plainUserName + "], policy["
                    + policyName + "].";

            logAndThrowAFE(msg);
        }

        Map specMap = new HashMap();
        List<Authenticator> successAuthenticators = new ArrayList<Authenticator>();
        for (AuthenticationAssertion assertion : assertions) {
            String keyID = Convert.toBase64(assertion.getSignResponse().getSignData().getKeyID());
            String aaid = assertion.getSignResponse().getSignData().getAAID();
            if(Strings.isNullOrEmpty(aaid)){
                aaid = "004A#FFF3";
                assertion.getSignResponse().getSignData().withAAID(aaid);
            }
            LOG.debug("KeyID: {}, AAID:{}, UserName:{}", keyID, aaid, plainUserName);

            if ((Strings.isNullOrEmpty(keyID)) || (Strings.isNullOrEmpty(aaid))) {
                LOG.error("keyID and AAID must be present in authenticator signed data.");
                throw new AuthSignAssertionValidationException(UAFErrorCode.PROTOCOL_INVALID_AUTH_ASSERTION);
            }
            Authenticator token = null;
            AuthenticatorSpec spec = null;
            Authenticator authenticator = null;
            try {
                // 根据appid、uasername、aaid、keyid查询公钥等数据
                authenticator = getAuthenticator(appID, hashedUserName, aaid, keyID,transType);
                if (authenticator == null) {
                    LOG.error("Couldn't find registered authenticator for the given keyID[{}]",
                            keyID);
                    throw new AuthenticatorNotRegisteredException(UAFErrorCode.BIZ_AUTHENTICATOR_NOT_FOUND);
                }
                // 获取认证数据中的元数据
                spec = authenticator.getMetadata();
                if (spec == null) {
                    LOG.error("Couldn't find metadata for authenticator for user[" + authenticator.getUserName()
                            + "]");
                    throw new MetadataNotFoundException(UAFErrorCode.DB_QUERY_METADATA_FAILED);
                }
                // assertion 断言处理
                token = processAssertion(response, assertion, authenticator,ext,functionType);
            } catch (Exception ex) {
                if (ex instanceof AdditionUviException) {
                    throw (AdditionUviException) ex;
                } else {
                    LOG.error("Authentication assertion failed for AAID[{}]", aaid, ex);
                    if (ex instanceof UAFException) {
                        UAFException uafe = (UAFException) ex;
                        IErrorCode ec = uafe.getErrorCode();
                        throw new UAFException(ec, ex.getMessage());
                    } else {
                        String msg = "Failed to validate [" + assertions.size() + "] authentication assertion(s).";
                        logAndThrowAFE(msg);
                    }
                }
            }

            if (token != null) {
                successAuthenticators.add(token);
                // 将AAID 与 其对应的 元数据信息 map
                specMap.put(spec.getAAID().format(), spec);
            }
        }

        if ((successAuthenticators != null) && (!successAuthenticators.isEmpty())) {

            checkIfDifferentUser(successAuthenticators);
            Authenticator authenticator = successAuthenticators.get(0);
            Policy policy = this.policyManager.getNamedPolicy(authenticator.getAuthType(), appID, transType);
            verifyAuthenticationPolicy(successAuthenticators, assertions, specMap, policy);
            if(Strings.isNotBlank(functionType) && Constants.FUNCTION_TYPE_UPDATE_CERT.equals(functionType)){
                Connection conn = null;
                try {
                    conn = UAFDBConnectionMgr.getConnection(true);
                    AuthenticatorDAO adao = UAFDAOFactory.createAuthenticatorDAO(conn);
                    // 插入证书更新的记录
                    adao.insertCertUpdateRecord(authenticator.getUserName(),authenticator.getCustNo(),authenticator.getDeviceID(),authenticator.getAppID(),authenticator.getAuthType(),authenticator.getTransType());
                } catch (SQLException sqle) {
                    throw new DAOException(UAFErrorCode.DB_DATABASE_CONNECTION_ERROR, sqle);
                } finally {
                    DBUtil.close(conn);
                }
            }
        } else {
            String msg = "Failed to validate [" + assertions.size() + "] authentication assertion(s).";
            logAndThrowAFE(msg);
        }

    }

    protected Authenticator processAssertion(AuthenticationResponse r, AuthenticationAssertion assertion,
                                   Authenticator authenticator,String ext,String functionType)
            throws ValidationException, DAOException, PolicyVerificationException, MetadataNotFoundException,
            TLVParsingException, CertificateValidationException, SignatureValidationException,
            TxnContentValidationException, IOException, ChallengeStoreException, ExactMatchNotFoundException, AdditionUviException, PluginException {

        ServerData serverData = r.getOperationHeader().getServerData();
        if (this.getServerConfig().getPerformModel() < Constants.PERFORM_MODEL_LEVEL_3 && !ServerContext.getServerContext().isPerform()) {
            AuthSignAssertionValidator asv = new AuthSignAssertionValidator(authenticator, r.getFCPBase64(), serverData);
            asv.validate(assertion);
        }
        String strKeyID = Convert.toBase64(assertion.getSignResponse().getSignData().getKeyID());
        String aaid = assertion.getSignResponse().getSignData().getAAID();

        String scheme = assertion.getSignDataScheme();
        LOG.info("Sign data scheme: {}", scheme);

        ASMSignResponse response = assertion.getSignResponse();
        SignData signData = response.getSignData();

        byte[] signature = response.getSignature();

        TLVType2 signedDataType = new TLVType2() {
        };
        signedDataType.addTLVSegment(Constants.UAFV1AssertionTag.TAG_UAFV1_SIGNED_DATA.getTagValue(),
                signData.getReceivedBytes());
        byte[] signedData = signedDataType.getTLVBytes();

        String strPubKey = authenticator.getPubKey();
        if (Strings.isNullOrEmpty(strPubKey)) {
            LOG.error("Failed to locate public key associate with AAID[{}], keyID[{}]",
                    aaid, strKeyID);
            throw new AuthenticationFailedException("Failed to locate public key associate with AAID[" + aaid + "]");
        }
        byte[] pubKey = Convert.fromBase64(authenticator.getPubKey());

        if (this.getServerConfig().getPerformModel() < Constants.PERFORM_MODEL_LEVEL_3 && !ServerContext.getServerContext().isPerform()) {
            if(signData.getSignAlgorithm() == 7){
                boolean signFlag = SM2Utils.verifySignRaw(pubKey, signedData, signature);
                if (!signFlag) {
                    throw new SignatureValidationException(UAFErrorCode.PROTOCOL_SIGNATURE_VALIDATION_FAILED);
                }
            }else {
                // 验证签名
                validateSignature(authenticator.getKeyFormat(), pubKey, signData.getSignAlgorithm(), signedData, signature);
            }
            LOG.info("Successfully verified signature for username[{}]", authenticator.getUserName());
        }
        List<Extension> exts = assertion.getExtensions();
        if ((exts != null) && (!exts.isEmpty())) {
            processExtensions(exts);
        }

        int signCounter = signData.getSignCounter();
        authenticator.setSignCounter(signCounter);
        Connection conn = null;
        try {
            conn = UAFDBConnectionMgr.getConnection(true);
            AuthenticatorDAO adao = UAFDAOFactory.createAuthenticatorDAO(conn);
            int rowsupdated = adao.updateSignCounter(signCounter, authenticator.getAppID(), authenticator.getUserName(), aaid, strKeyID);
            if (rowsupdated != 1) {
                LOG.error("Failed to update sign counter for AAID [{}], keyID[{}]", aaid, strKeyID);
                throw new AuthenticationFailedException("Failed to update sign counter for AAID[" + aaid + "]");
            }
        } catch (SQLException sqle) {
            throw new DAOException(UAFErrorCode.DB_DATABASE_CONNECTION_ERROR, sqle);
        } finally {
            DBUtil.close(conn);
        }
        LOG.debug("Successfully updated the signCounter for AAID[{}] to value [{}]",
                aaid, Integer.valueOf(signCounter));

        if(authenticator.getAuthType().equals(Constants.AuthType.GESTUREPRINT.getAuthType())){
            byte[] uviBytes = signData.getUvi();
            byte[] keys = Arrays.copyOf(CryUtil.getSHA256(Convert.toBase64(r.getOperationHeader().getServerData().getChallenge()).getBytes()),16);
            byte[] aes256Decode = CryUtil.Aes256Decode(uviBytes,keys);
            processExactMatchGesture(aes256Decode, authenticator);
        }else{
            // 精确匹配处理，如果注册的是A指纹，但是认证使用的是B指纹（前提：手机中A、B指纹都已经提前录入了）
            processExactMatch(signData.getUvi(), authenticator);
        }


        return authenticator;
    }
    protected void processExactMatchGesture(byte[] uviBytes, Authenticator authenticator)
            throws DAOException, ExactMatchNotFoundException, AdditionUviException {
        String uvi = null;
        if ((uviBytes != null) && (uviBytes.length > 0)) {
            uvi = Convert.toBase64(uviBytes);
        }
        if (uvi == null) {
            if (authenticator.getUvi() != null) {
                LOG.error(
                        "Exact match cannot be done, UVI data is missing in auth response for AAID[{}].",
                        authenticator.getAAID());

                throw new ExactMatchNotFoundException(
                        UAFErrorCode.PROTOCOL_EXACT_MATCH_NOT_FOUND);
            }
        } else if (uvi.equals(authenticator.getUvi())) {
            LOG.info(
                    "UVI validated successfully for exact match for user [{}].",
                    authenticator.getUserName());
        } else {
            LOG.error(
                    "Exact match failed for AAID[{}], uvi[{}] user is [{}].",
                    authenticator.getAAID(), uvi,
                    authenticator.getUserName());

            throw new ExactMatchNotFoundException(
                    UAFErrorCode.PROTOCOL_EXACT_MATCH_NOT_FOUND);
        }

    }
    protected void processExactMatch(byte[] uviBytes, Authenticator authenticator)
            throws DAOException, ExactMatchNotFoundException, AdditionUviException {
        String uvi = null;

        // 是否开启了指纹精确匹配
        if (serverConfig.isExactMatchRequired(authenticator.getAppID())) {
            if ((uviBytes != null) && (uviBytes.length > 0)) {
                uvi = Convert.toBase64(uviBytes);
            }
            if (uvi == null) {
                if (authenticator.getUvi() != null) {
                    LOG.error(
                            "Exact match cannot be done, UVI data is missing in auth response for AAID[{}].",
                            authenticator.getAAID());

                    throw new ExactMatchNotFoundException(
                            UAFErrorCode.PROTOCOL_EXACT_MATCH_NOT_FOUND);
                }
            } else if (uvi.equals(authenticator.getUvi())) {
                LOG.info(
                        "UVI validated successfully for exact match for user [{}].",
                        authenticator.getUserName());
                // 是否开启了指纹集匹配
            } else if (getServerConfig().isUvsMatchRequired(authenticator.getAppID())) {
                if (null != authenticator.getUvs() && authenticator.getUvs().contains(uvi)) {
                    LOG.info(
                            "UVI validated successfully for uvs match for user [{}].",
                            authenticator.getUserName());
                } else {
                    //if (null == authenticator.getUvs() || authenticator.getUvs().size() < 5) {
                        AdditionUvi additionUvi = new AdditionUvi();
                        additionUvi.setAaid(authenticator.getAAID().toString());
                        additionUvi.setAppID(authenticator.getAppID());
                        additionUvi.setKeyID(Convert.toBase64(authenticator.getKeyID().getId()));
                        additionUvi.setUserName(authenticator.getUserName());
                        additionUvi.setTime(System.currentTimeMillis());
                        additionUvi.setUvi(uvi);

                        String additionUviJson = UAFSchemaBuilder.getGson().toJson(additionUvi);
                        LOG.info("Addition uvi :" + uvi + ",user :" + authenticator.getUserName() + ",json:" + additionUviJson);

                        String additionUviBase64 = Convert.toBase64(CryUtil.encryptAES128(additionUviJson.getBytes(), serverConfig.getEncryptionKey()));
                        if(authenticator.getAuthType().equals(Constants.AuthType.FINGERPRINT.getAuthType())) {
                            throw new AdditionUviException(UAFErrorCode.PROTOCOL_ADD_UVI, "", additionUviBase64);
                        }else {
                            throw new ExactMatchNotFoundException(
                                    UAFErrorCode.PROTOCOL_EXACT_MATCH_NOT_FOUND);
                        }
//                    } else if (authenticator.getUvs().size() >= 5) {
//                        LOG.info("Addition uvi :" + uvi + ",user :" + authenticator.getUserName() + ", exceed max num");
//
//                        throw new AdditionUviException(UAFErrorCode.PROTOCOL_ADD_UVI_MAX_LENGTH);
//                    }
                }
            } else {
                LOG.error(
                        "Exact match failed for AAID[{}], uvi[{}] user is [{}].",
                        authenticator.getAAID(), uvi,
                        authenticator.getUserName());

                throw new ExactMatchNotFoundException(
                        UAFErrorCode.PROTOCOL_EXACT_MATCH_NOT_FOUND);
            }
        }

    }

    private String checkIfDifferentUser(List<Authenticator> aList) {
        String user = null;
        for (Authenticator a : aList) {
            if (user != null) {
                String user2 = a.getUserName();
                if (!user.equalsIgnoreCase(user2)) {
                    throw new AuthenticationFailedException(
                            "All Authenticator Assestions should be for same user, found different[" + user + "]["
                                    + user2 + "]");
                }

            }

            user = a.getUserName();
        }
        return user;
    }

    protected void verifyAuthenticationPolicy(List<Authenticator> retval, List<AuthenticationAssertion> assertions,
                                            Map<String, AuthenticatorSpec> specMap, Policy p) throws PolicyVerificationException {

        List<AuthenticationAssertion> policyVerifiedAssertions = PolicyVerificationProcessor
                .verifyAuthenticationPolicy(assertions, specMap, p);

        if (policyVerifiedAssertions == null) {
            LOG.error("No assertions were matched against the policy : " + p.getPolicyName());
            throw new AuthenticationFailedException(
                    "No assertions were matched against the policy : " + p.getPolicyName());
        }

        List<String> verifiedAAIDs = new ArrayList<String>();
        for (AuthenticationAssertion assertion : policyVerifiedAssertions) {
            verifiedAAIDs.add(assertion.getSignResponse().getSignData().getAAID());
        }

        for (Iterator<Authenticator> authenticatorIter = retval.iterator(); authenticatorIter.hasNext(); ) {
            Authenticator authenticator = authenticatorIter.next();
            if (verifiedAAIDs.contains(authenticator.getAAID().format())) {
                LOG.info("Success in policy verification for AAID : " + authenticator.getAAID().format());
            } else {
                LOG.error("Failed(or not required) in policy verification for AAID : "
                        + authenticator.getAAID().format());
                LOG.error("Removing from the list as this assertion was not used for policy verification.");
                authenticatorIter.remove();
            }
        }
    }

    protected void logAndThrowAFE(String msg) {
        LOG.error(msg);
        throw new AuthenticationFailedException(msg);
    }

}
